package core;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;

import javax.imageio.ImageIO;


public class Utils {
	//dictates how much memory an image can take up of free memory (we have to leave room for the energy and seams)
	public static final double MAX_MEM_PER = .25;
	
	//get all available write formats
	public static final ArrayList<String> FORMATS = new ArrayList<String>(Arrays.asList(ImageIO.getWriterFormatNames()));

	//specifies the prefix for tmp files
	private static final String TMP_FILE_PREFIX = "SeamCarve";

	
	
	public static void main(String[] Args) throws IllegalArgumentException, IOException {
		//attempt to short out memory
		/*BufferedImage[] list = new BufferedImage[10000];
		
		for (int i = 0; i < list.length; i++)
			list[i] = getImage("/Users/gregdicristofaro/Pictures/background/landscape/02207_venividivenice_1280x800.jpeg");
		
		for (int i = 0; i < list.length; i++)
			System.out.println(list[i].getMinX());
		 */
	
		//test of reading and writing
		/*writeImage(getImage("/Users/gregdicristofaro/Pictures/background/landscape/02207_venividivenice_1280x800.jpeg"), 
				"BMP", "/Users/gregdicristofaro/Desktop/stuff.bmp");
		*/
	}
	
	
	
	
	/**
	 * gets an image from a path
	 * 
	 * @param path						the string of the path
	 * @return							the BufferedImage
	 * 
	 * @throws IOException 				thrown when there is a problem reading the file	
	 * @throws IllegalArgumentException	thrown when image would take up too much memory
	 */
	public static BufferedImage getImage (String path) throws IOException, IllegalArgumentException {
		//get the file
		File file = new File(path);
		
		//check the size of file
		double filesize = file.length();

		//http://stackoverflow.com/questions/5512378/how-to-get-ram-size-and-size-of-hard-disk-using-java
		double freeMemory = Runtime.getRuntime().freeMemory();
		
		if (filesize / freeMemory > MAX_MEM_PER)
			throw new IllegalArgumentException("This image has size of " 
					+ filesize + " which will take up too much memory.");
		
		
		//if we are good to go, read it in
		return ImageIO.read(file);
	}
	
	
	
	/**
	 * gets an image from input stream
	 * 
	 * @param istream					input stream to convert to image
	 * @return							the BufferedImage
	 * 
	 * @throws IOException 				thrown when there is a problem reading the file	
	 * @throws IllegalArgumentException	thrown when image would take up too much memory
	 */
	public static BufferedImage getImage (InputStream istream) throws IOException, IllegalArgumentException {
		//if we are good to go, read it in
		BufferedImage img = ImageIO.read(istream);
		
		//http://stackoverflow.com/questions/5512378/how-to-get-ram-size-and-size-of-hard-disk-using-java
		double freeMemory = Runtime.getRuntime().freeMemory();
		
		////using http://stackoverflow.com/questions/8351155/check-how-much-memory-bufferedimage-in-java-uses
		DataBuffer buff = img.getRaster().getDataBuffer();
		double filesize = buff.getSize() * DataBuffer.getDataTypeSize(buff.getDataType()) / 8;
		
		if (filesize / freeMemory > MAX_MEM_PER)
			throw new IllegalArgumentException("This image has size of " 
					+ filesize + " which will take up too much memory.");
		
		return img;
	}

	
	/** Writes bufferedimage to disk
	 * 
	 * @param img							the image to be saved
	 * @param type							the type of Image (look to FORMATS if problem)
	 * @param path							the String as a path
	 * @return								returns true if successful
	 * 
	 * @throws IOException					thrown when problem writing
	 * @throws IllegalArgumentException		thrown when type is unknown to ImageIO
	 */
	public static boolean writeImage (BufferedImage img, String type, String path) throws IOException, IllegalArgumentException {
		if (!FORMATS.contains(type))
			throw new IllegalArgumentException("Image Type of " + type + " is not known to ImageIO");
		
		return ImageIO.write(img, type, new File(path));	
	}
	
	
	/** Writes bufferedimage to stream
	 * 
	 * @param img							the image to be written
	 * @param type							the type of Image (look to FORMATS if problem)
	 * @param ostream						the stream to write to
	 * @return								returns true if successful
	 * 
	 * @throws IOException					thrown when problem writing
	 * @throws IllegalArgumentException		thrown when type is unknown to ImageIO
	 */	
	public static boolean writeImage (BufferedImage img, String type, OutputStream ostream) throws IOException {
		if (!FORMATS.contains(type))
			throw new IllegalArgumentException("Image Type of " + type + " is not known to ImageIO");
		
		return ImageIO.write(img, type, ostream);
	}
	
	/**
	 * Resizes the image
	 * 
	 * @param img			the image to be resized
	 * @param newHeight		the new height
	 * @param newWidth		the new width
	 * @return				the resized bufferedimage
	 */
	public static BufferedImage resizeImage (BufferedImage img, int newHeight, int newWidth) {
		//TODO this will get the job done, but there is probably a better way to do this
		
		//taken from http://www.javalobby.org/articles/ultimate-image/
		int w = img.getWidth();
		int h = img.getHeight();
		BufferedImage dimg = new BufferedImage(newWidth, newHeight, img.getType());
		Graphics2D g = dimg.createGraphics();
		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		g.drawImage(img, 0, 0, newWidth, newHeight, 0, 0, w, h, null);
		g.dispose();
		return dimg;
	}
	
	
	/**
	 * writes an image to a temporary file and returns the file
	 * 
	 * @param img				the image
	 * @return					the file
	 * 
	 * @throws IOException		thrown if error writing
	 */
	public static File imageToTempFile(BufferedImage img) throws IOException {
		//from http://stackoverflow.com/questions/4863658/how-to-get-system-time-in-java-without-creating-a-new-date
		long currentTime = System.currentTimeMillis();
		File file = File.createTempFile(TMP_FILE_PREFIX + currentTime, "png");
		file.deleteOnExit();
		ImageIO.write(img, "png", file);
		return file;
	}
	
	/**
	 * get RGB int value from RGB values
	 * 
	 * @param red		red value
	 * @param green		green value
	 * @param blue		blue value
	 * 
	 * @return			int value
	 */
	public static int getIntFromRGB(int red, int green, int blue) {
		if (red > 255 || red < 0 || green > 255 || green < 0 || blue > 255 || blue < 0)
			throw new IllegalArgumentException("rgb value out of bounds  red: " + red + ", green: " + green + " , blue: " + blue);
		
		return (red * 256 * 256 + green * 256 + blue);
	}
	
	
	/**
	 * Class that gives RGB value breakdown from an int value
	 */
	public static class RGB {
		public final int red;
		public final int green;
		public final int blue;
		
		
		public RGB(int val) {
			// taken from here: http://stackoverflow.com/questions/2183240/java-integer-to-byte-array
			byte[] bytes = ByteBuffer.allocate(4).putInt(val).array();
			
			//taken from http://stackoverflow.com/questions/7401550/how-to-convert-int-to-unsigned-byte-and-back
			red = bytes[1] & 0xFF;
			green = bytes[2] & 0xFF;
			blue = bytes[3] & 0xFF;
			
			/*//for testing purposes
			if (red > 255 || red < 0 || green > 255 || green < 0 || blue > 255 || blue < 0)
				System.out.println("red is: " + red + " green is: " + green + " blue is: " + blue + " and something else: " + bytes[0]);
			*/
		}
		
	}
}